/**
	Pulsar engine. Core.
	Base application class - windows extension

	Copyright (C) 2013-2014 Dmitry Dolgov

	This program is free software; you can redistribute it and/or modify it under the terms of the
	GNU General Public License as published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
	without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
	See the GNU General Public License for more details.
*/

#if !defined(PL_WINDOWS) && !defined(PL_WINDOWS64)
#error "This file can be user for Windows applications only"
#endif

#include <windows.h>
#include <mmsystem.h>
#include <shlobj.h>
#include <objbase.h>
#include <shlwapi.h>
#include <tlhelp32.h>

#include <pulsar/pulsar.h>
#include <pulsar/src/win/pulsar_intr_win.h>
#include <pulsar/src/win/pulsar_debug_win.h>

#if defined(PL_GCC)
// All additional libraries must be specified in target application build options:
// linwinmm.a
#elif defined(PL_MSC)
#pragma comment(lib, "kernel32.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "comdlg32.lib")
#pragma comment(lib, "shell32.lib")
#pragma comment(lib, "winmm.lib")
#else
#error "Undefined compiler"
#endif

namespace pl
{

App* App::app = NULL;
DWORD _timeAppStart = 0;

} // namespace pl


pl_uint32 pl::getTimeMS()
{
	return (pl_uint32)timeGetTime() - _timeAppStart;
}
pl_float pl::getTimeSec()
{
	return (pl_float)(timeGetTime() - _timeAppStart) / 1000.f;
}

/*
	Windows application ctor
*/
pl::App::App() :
_hInstance(NULL)
{
	if (app != NULL)
	{
		//
		// Only one copy of Application class is allowed in system
		//
		PL_BREAK();
	}
	app = this;
	return;
}

/*
	Windows application dtor
*/
pl::App::~App()
{
	if (app != this)
	{
		//
		// Onle one copy of Application class is allowed in system
		//
		PL_BREAK();
	}
	app = NULL;
	return;
}

const pl::Filepath& pl::App::getStdPath(StarndardPath path)
{
	static pl::Filepath dummy;

	switch (path)
	{
		case STANDARD_PATH_APPROOT:
			return _appRootFolder;
		case STANDARD_PATH_EXENAME:
			return _exeNameFolder;
		case STANDARD_PATH_SYSTEM:
			return _systemFolder;
		case STANDARD_PATH_PROGRAMS:
			return _programsFolder;
		case STANDARD_PATH_USERHOME:
			return _userHomeFolder;
		case STANDARD_PATH_DOCUMENTS:
			return _documentsFolder;
		case STANDARD_PATH_SETTINGS:
			return _settingsFolder;
		case STANDARD_PATH_TEMP:
			return _tempFolder;
	}
	PL_ASSERTE(FALSE, PL_T("Bad path query"));
	return dummy;
}

bool pl::App::getCurDateTime(DateTime& dateTime)
{
	SYSTEMTIME  sysTime;

	GetLocalTime(&sysTime);

	// TODO - convert using special function
	dateTime.msecond = (pl_uint16)sysTime.wMilliseconds;
	dateTime.second  = (pl_uint8)sysTime.wSecond;
	dateTime.minute  = (pl_uint8)sysTime.wMinute;
	dateTime.hour    = (pl_uint8)sysTime.wHour;
	dateTime.day     = (pl_uint8)sysTime.wDay;
	dateTime.month   = (pl_uint8)sysTime.wMonth;
	dateTime.year    = (pl_uint16)sysTime.wYear;
	return true;
}

/*
	Terminate process and exit abnormally
*/
void pl::App::terminateProcess(int exitCode)
{
	TerminateProcess(GetCurrentProcess(), exitCode);
	return;
}

#if defined(PL_ENTRY_POINT_INTERNAL)
//
// Use entry point specified internally in pulsar code
//

#if defined(PL_DOS)

/*
	Windows console application default entry point
*/
int main(int argc, char* argv[])
{
	return pl::App::app->_main(argc, (const char**)argv);
}

int pl::App::_main(int argc, const char* argv[])
{
	_hInstance = GetModuleHandle(NULL);

	_initCore1();
	_initWin();
	_initCore2(PL_NEW CommandLine(argc, argv, 1));

	int rc = mainLoop();

	_termCore2();
	_termWin();
	_termCore1();
	return rc;
}

#elif defined(PL_DLL)

BOOL WINAPI DllMain(HANDLE hInstance, DWORD dwReason, LPVOID lpvReserved)
{
	_hInstance = _hInstance;
	return pl::App::app->_dllMain(hInstance, dwReason);
}

BOOL pl::App::_dllMain(HANDLE hInstance, DWORD dwReason)
{
	// TODO
	return TRUE;
}

#else

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	return pl::App::app->_winMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}

int pl::App::_winMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	_hInstance = _hInstance;

	_initCore1();
	_initWin();
	_initCore2(PL_NEW CommandLine(lpCmdLine, 1));

	int rc = mainLoop();

	_termCore2();
	_termWin();
	_termCore1();
	return rc;
}

#endif

#elif defined(PL_ENTRY_POINT_EXTERNAL)

//
// Use external entry point compilation method
// No additional entry point needed
//
pl::StartupFunc& pl::App::getBaseSettings()
{
	static pl::StartupFunc info;
	return info;
}
bool pl::App::init(int argc, const pl_char* argv[])
{
	_initCore1();
	_initWin();
	_initCore2(PL_NEW CommandLine(argc, argv, 1));
	return true;
}
bool pl::App::init(const pl_char* cmdLine)
{
	_initCore1();
	_initWin();
	_initCore2(PL_NEW CommandLine(cmdLine, 1));
	return true;
}
void pl::App::term()
{
	_termCore2();
	_termWin();
	_termCore1();
}

#endif

/*
*/
void pl::App::_initWin()
{
	pl_char folderName[MAX_PATH];
	pl_char comName[MAX_COMPUTERNAME_LENGTH + 1];

	timeBeginPeriod(1);
	_timeAppStart = timeGetTime();

	AppDebugWin::init();

	// Setup standard folders
	if (GetModuleFileName(GetModuleHandle(NULL), folderName, sizeof(folderName)))
	{
		_exeNameFolder.set(folderName);
	}
	if (PathRemoveFileSpec(folderName))
	{
		_appRootFolder.set(folderName);
	}
	if (SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, folderName) == S_OK)
	{
		_settingsFolder.set(folderName);
	}
	if (SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, folderName) == S_OK)
	{
		_documentsFolder.set(folderName);
	}
	if (SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, SHGFP_TYPE_CURRENT, folderName) == S_OK)
	{
		_programsFolder.set(folderName);
	}
	if (SHGetFolderPath(NULL, CSIDL_PROFILE, NULL, SHGFP_TYPE_CURRENT, folderName) == S_OK)
	{
		_userHomeFolder.set(folderName);
	}
	if (GetTempPath(MAX_PATH, folderName))
	{
		_tempFolder.set(folderName);
	}
	if (GetSystemDirectory(folderName, MAX_PATH))
	{
		_systemFolder.set(folderName);
	}
	DWORD size = sizeof(comName);
	if (GetComputerName(comName, &size))
	{
		_compName.set(comName);
	}

	if (GetLocaleInfo(LOCALE_SYSTEM_DEFAULT, LOCALE_SLANGDISPLAYNAME, folderName, MAX_PATH))
	{
		_langName.set(folderName);
	}
	return;
}

/*
*/
void pl::App::_termWin()
{
	timeEndPeriod(1);
	AppDebugWin::term();
	return;
}

/*
*/
pl::HandleResult pl::App::_onAssert(const pl_char* file, pl_uint32 line, const pl_char* func, const pl_char* cond, const pl_char* msg, pl::AssertType type)
{
	return AppDebugWin::onAssert(file, line, func, cond, msg, type);
}

/*
*/
pl::HandleResult pl::App::_onMemErr(const pl_char* file, pl_uint32 line, const pl_char* func, void* p, pl_int64 size, pl::MemoryError idEvent)
{
	return AppDebugWin::onMemErr(file, line, func, p, size, idEvent);
}


void pl::App::_onTrace(const pl_char* file, pl_uint32 line, const pl_char* msg)
{
	pl::String<1024> str;

	str << file << PL_T('(') << line << PL_T("): ") << msg << PL_T('\n');
	OutputDebugString(str.get());
	return;
}

//
// End of file 'pulsar_app_win.cpp'
//
